package com.aldobucchi.x.flash.geom
{
	
	
	import flash.display.DisplayObject;
	import flash.geom.Matrix;
	import flash.geom.Point;
	
	import mx.utils.StringUtil;
	
	import com.aldobucchi.x.flash.geom.decompose.SVDDecomposition;
	
	
	
	/**
	 * 
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class Transformation2D
	{
		
		
		
		public static const IDENTITY:Transformation2D = new Transformation2D( 0, 0, 1, 1, 0 );
		
		
		
		
		
		public var tx:Number;
		
		public var ty:Number;
		
		public var scaleX:Number;
		
		public var scaleY:Number;
		
		public var rotationRadians:Number;		
		


		public var rotationDegrees:Number;



		/**
		 * 
		 * @return 
		 * 
		 */
		public function get translationAsPoint( ):Point
		{
			return new Point( this.tx, this.ty );	
		}


		/////////////////////////////////////////////////////////
		// matrix
		/////////////////////////////////////////////////////////


		// internal so it can be set by static factory method
		internal var _matrix:Matrix;
		
		public function get matrix( ):Matrix
		{
			if ( !_matrix )
			{

				_matrix = new Matrix( );

				_matrix.rotate( rotationRadians );
				_matrix.scale( scaleX, scaleY );
				_matrix.translate( tx, ty );	
			
			}

			return _matrix;		
		}






		
		
		public function Transformation2D( tx:Number, ty:Number, scaleX:Number, scaleY:Number, rotationRadians:Number )
		{
			this.tx = tx;
			this.ty = ty;
			this.scaleX = scaleX;
			this.scaleY = scaleY;
			this.rotationRadians = rotationRadians;
			
			
			// calculated
			this.rotationDegrees = GeomUtil.radiansToDegrees( rotationRadians );

		}




		/**
		 * 
		 * 
		 * 
		 * @param target
		 * 
		 */
		public function applyUsingSetters( target:DisplayObject ):void
		{
			target.rotation += rotationDegrees;			

			target.scaleX	*= scaleX;			
			target.scaleY	*= scaleY;

			target.x 		+= tx;
			target.y		+= ty;

		}



		public function setUsingSetters( target:DisplayObject ):void
		{
			target.rotation = rotationDegrees;	
					
			target.scaleY	= scaleY;
			target.scaleX	= scaleX;			

			target.x 		= tx;
			target.y		= ty;

		}




		
		
		/**
		 * 
		 * returns a "diff"erence trans such that
		 * <pre>
		 * this - other = diff
		 * </pre>
		 * 
		 * @param otherTrans
		 * @return 
		 * 
		 */		
		public function subtract( other:Transformation2D ):Transformation2D
		{
		
			return new Transformation2D(
					
					this.tx - other.tx ,			
					this.ty - other.ty ,			
					this.scaleX / other.scaleX ,			
					this.scaleY / other.scaleY ,			
					this.rotationRadians - other.rotationRadians
					
				 );
		
		}




		/**
		 * 
		 * Returns a sum trans such that
		 * <pre>
		 * this + other = sum
		 * </pre>
		 * 
		 * @param other
		 * @return 
		 * 
		 */
		public function add( other:Transformation2D ):Transformation2D
		{
		
			return new Transformation2D(
					
					this.tx + other.tx ,			
					this.ty + other.ty ,			
					this.scaleX * other.scaleX ,			
					this.scaleY * other.scaleY ,			
					this.rotationRadians + other.rotationRadians
					
				 );			
		
		}




		public function getInverse( ):Transformation2D
		{
		
			return new Transformation2D(
					
					tx * -1 ,			
					ty * -1 ,			
					1 / scaleX ,			
					1 / scaleY ,			
					rotationRadians * -1
					
				 );				
					
		
		}




		public function similar( other:Transformation2D, maxTranslationError:Number = 1e-2, maxRotationError:Number = 1e-1, maxScaleError:Number = 1e-1 ):Boolean
		{
			
			
			var distance:Number = Point.distance( this.translationAsPoint, other.translationAsPoint );

			if ( distance > maxTranslationError )
				return false;		


			var rotationDiff:Number = Math.abs( this.rotationRadians - other.rotationRadians );
			
			if ( rotationDiff > maxRotationError )
				return false;
				
			
			
			if ( Math.abs( this.scaleX / other.scaleX - 1 ) > maxScaleError )
				return false;
				
			
			if ( Math.abs( this.scaleY / other.scaleY - 1 ) > maxScaleError )
				return false;
			
			
			return true;				
		}





		
		public function toString( ):String
		{
		
			return StringUtil.substitute(
				" tx:{0}, ty:{1}, scaleX:{2}, scaleY:{3}, rotation:{4}",
				tx, ty, scaleX, scaleY, rotationRadians
			 );
		
		
		
		}







		/**
		 * 
		 * Regular means: no skewing and proportional scaling.
		 * We are using SVD internally.
		 * 
		 * @param matrix
		 * @return 
		 * 
		 */
		public static function approximateFromRegularMatrix( matrix:Matrix ):Transformation2D
		{
			
			
			var svd:SVDDecomposition = SVDDecomposition.decompose( matrix );
			
			var trans:Transformation2D = new Transformation2D(
				svd.dx, svd.dy, svd.sx, svd.sy, - svd.angle1 
				);
			
			trans._matrix = matrix.clone( ); // maximize fidelity
		
			return trans;
		}
		


	}


}